home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
MacWorld: Conference & Expo 2005
/
Macworld Conference and Expo - Documentation CD-ROM (2004).iso
/
mac
/
Adobe Reader 6.0
/
Adobe Reader 6.0.app
/
Contents
/
MacOS
/
JavaScripts
/
media.js
< prev
Wrap
Text File
|
2004-01-30
|
66KB
|
2,381 lines
// ADOBE SYSTEMS INCORPORATED
// Copyright 2003 Adobe Systems Incorporated
// All Rights Reserved
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
// If you have received this file from a source other than Adobe, then your use,
// modification, or distribution of it requires the prior written permission of Adobe.
// media.js - Adobe Acrobat multimedia support
// The app and app.media properties and methods in this file are part of the public Acrobat
// Multimedia API and may be used in your PDF JavaScript, EXCEPT where the name includes "priv"
// or is otherwise noted as private. DO NOT USE any of these private properties or methods in
// a PDF file, or YOUR PDF WILL BREAK in a future version of Acrobat.
// Greetings and thanks from the Acrobat Multimedia development team:
// Dylan Ashe - Multimedia Framework and QuickTime
// Michael Geary - JavaScript, Windows Media, Flash
// Scott Grant - User Interface, Authoring, Sound, PDF File Access
// Vivek Hebbar - Windows Built-in Player and Authoring
// Liz McQuarrie - RealOne Player and Browsers
// Ed Rowe - Project Lead and Mr. PDF
// Jason Beique, Paul Herrin, Renato Maschion, Jason Reuer, Xintai Chang - QA and Developer Tech
// Multimedia version number
console.println( 'Acrobat Multimedia Version 6.0' );
app.media.version = 6.0;
// Set app.media.trace = true to enable method and event tracing in this code.
// Note: app.media.trace is for testing only and will change in future versions.
app.media.trace = false;
// The app.media.* constants below are passed back and forth between C++ and JavaScript code.
// Always use these symbolic definitions instead of hard coded values, e.g.
// settings.windowType = app.media.windowType.floating; /* NOT settings.windowType = 2; */
// PDF files may be opened under both newer and older versions of Acrobat.
// Future versions of Acrobat may add new values to these lists. Your JavaScript code should
// gracefully handle any values it encounters beyond those listed here.
// Similarly, if you write JavaScript code for a future version of Acrobat, that code should
// check app.media.version before it depends on new app.media.* constant values added in
// that version. The lists below are marked to indicate which versions support which constants.
// Values for settings.layout
app.media.layout =
{
meet: 1, // scale to fit all content, preserve aspect, no clipping, background fill
slice: 2, // scale to fill window, preserve aspect, clip X or Y as needed
fill: 3, // scale X and Y separately to fill window
scroll: 4, // natural size with scrolling
hidden: 5, // natural size with clipping
standard: 6 // use player's default settings
// End 6.0 values
}
// Values for settings.windowType
app.media.windowType =
{
docked: 1,
floating: 2,
fullScreen: 3
// End 6.0 values
}
// Values for settings.monitorType
app.media.monitorType =
{
document: 1,
nonDocument: 2,
primary: 3,
bestColor: 4,
largest: 5,
tallest: 6,
widest: 7
// End 6.0 values
}
// Values for settings.floating.align
app.media.align =
{
topLeft: 1,
topCenter: 2,
topRight: 3,
centerLeft: 4,
center: 5,
centerRight: 6,
bottomLeft: 7,
bottomCenter: 8,
bottomRight: 9
// End 6.0 values
}
// Values for settings.floating.canResize
app.media.canResize =
{
no: 1,
keepRatio: 2,
yes: 3
// End 6.0 values
}
// Values for settings.floating.over
app.media.over =
{
pageWindow: 1,
appWindow: 2,
desktop: 3,
monitor: 4
// End 6.0 values
}
// Values for settings.floating.ifOffScreen
app.media.ifOffScreen =
{
allow: 1,
forceOnScreen: 2,
cancel: 3
// End 6.0 values
}
// Default value for settings.visible
app.media.defaultVisible = true;
// Values for rendition.type
app.media.renditionType =
{
unknown: 0, // rendition type not recognized by Acrobat
media: 1,
selector: 2
// End 6.0 values
}
// Values for event.media.code in Status event
app.media.status =
{ // event.media.text contains:
clear: 1, // empty string - clears any message
message: 2, // general message
contacting: 3, // hostname being contacted
buffering: 4, // percent complete
init: 5, // name of the player being initialized
seeking: 6 // nothing
// End 6.0 values
}
// Values for event.media.closeReason in Close event
app.media.closeReason =
{
general: 1,
error: 2,
done: 3,
stop: 4,
play: 5,
uiGeneral: 6,
uiScreen: 7,
uiEdit: 8,
docClose: 9,
docSave: 10,
docChange: 11
// End 6.0 values
}
// Values for player.open() return value
app.media.openCode =
{
success: 0,
failGeneral: 1,
failSecurityWindow: 2,
failPlayerMixed: 3,
failPlayerSecurityPrompt: 4,
failPlayerNotFound: 5,
failPlayerMimeType: 6,
failPlayerSecurity: 7,
failPlayerData: 8
// End 6.0 values
}
// Values for Error.raiseSystem
app.media.raiseSystem =
{
fileError: 10
}
// Values for Error.raiseCode
app.media.raiseCode =
{
fileNotFound: 17,
fileOpenFailed: 18
}
// In a PDF event, these event.name values indicate page-level actions.
app.media.pageEventNames =
{
Open: true,
Close: true,
InView: true,
OutView: true
}
// Create and return a MediaPlayer without opening it. Explicit values can be provided for all
// the arguments listed below. If this function is called from a rendition action, it will get
// the annot, rendition, and doc values from the event object if they are not explicitly provided.
// Returns player, or null on failure. Does not throw exceptions.
// Any failures are reported to user, and result in null being returned.
// Events may be fired as a result of closing an existing player (see below).
// Unless noStockEvents is true, stock event handlers are added to the returned player,
// and will be added to the annot (if present) when player.open() is called later.
// player = app.media.createPlayer({
// doc: Doc, /* Required if both annot and rendition are omitted, e.g. for URL playback */
// annot: ScreenAnnot, /* Required for docked playback unless it is found in the Event object
// or settings.page is provided. The new player is associated with the annot. If a player
// was already associated with the annot, it is stopped and closed. */
// rendition: MediaRendition or RenditionList, /* Required unless rendition found in Event,
// or URL is present */
// URL: String, /* Either URL or rendition is required, with URL taking precedence */
// mimeType: string, /* Optional, ignored unless URL is present. If URL is present, either
// mimeType or settings.players, as returned by app.media.getPlayers(), is required */
// settings: MediaSettings, /* Optional, overrides the rendition settings */
// events: EventListener, /* Optional (if stock events are used, added after stock events) */
// noStockEvents: Boolean, /* Optional, default = false, use stock events or not */
// fromUser: Boolean, /* Optional, default depends on Event object */
// showAltText: Boolean, /* Optional, default = true */
// showEmptyAltText: Boolean /* Optional, default= ! fromUser */
// });
app.media.createPlayer = function( args )
{
try
{
return app.media.priv.createPlayer( app.media.argsDWIM( args ) );
}
catch( e )
{
app.media.alert( 'Exception', args, { error: e } );
return null;
}
}
// Create, open, and return a MediaPlayer. See app.media.createPlayer() for argument details
// and other information.
// This method fires several events which may include Open, Ready, Play and Focus.
// Returns player, or null on failure. Does not throw exceptions.
// Any failures are reported to user.
app.media.openPlayer = function( args )
{
var player = null;
try
{
// Do our own DWIM here to make sure args.doc is set in case of error
args = app.media.argsDWIM( args );
player = app.media.createPlayer( args );
if( player )
{
var result = player.open();
if( result.code != app.media.openCode.success )
{
player = null;
app.media.alert( 'Open', args, { code: result.code } );
}
else if( player.visible )
player.setFocus(); // fires Focus event
}
}
catch( e )
{
player = null;
app.media.alert( 'Exception', args, { error: e } );
}
return player;
}
// Open a new media player using the current event or explicit args as in app.media.createPlayer().
// If an annot is provided or found in event object, and there is already a player open for that
// annot, then start or resume playback on that player.
// See app.media.openPlayer for argument details and other information.
// Returns player, or null on failure. Does not throw exceptions.
// Any failures are reported to user.
app.media.startPlayer = function( args )
{
try
{
args = app.media.argsDWIM( args );
var player = args.annot && args.annot.player;
if( player && player.isOpen )
player.play(); // already opened, resume play
else
player = app.media.openPlayer( args ); // open a new player
return player;
}
catch( e )
{
app.media.alert( 'Exception', args, { error: e } );
return null;
}
},
// app.media.Events constructor and prototype
// The Events constructor, events.add, and events.remove methods each takes any number of
// arguments, where each argument can be either an event listener object or a previously
// constructed app.media.Events object. The constructor and add() method add each listener
// object to the Events object, and the remove() method removes listener objects.
// An event listener object is a collection of event methods and optional custom properties or
// methods. Any method whose name matches /^on[A-Z]/ or /^after[A-Z]/ is an event method. Custom
// methods and properties in an event listener should use names that do not match these case
// sensitive patterns.
// When an event listener method is called, 'this' is the event listener object. The event
// listener can have custom properties like any other object. If an event listener is nested
// inside another function (such as a constructor), then the event methods can also directly
// access any variables defined in the parent function, even when the event method is called
// after the parent function returns.
// The same event listener object may be added into more than one Events object. The object is
// not copied; each Events object has a reference to the original event listener object, so any
// properties of the listener object are shared by every Events object it is added to.
// Implementation note:
// An app.media.Events object has a listeners property which is a object containing
// onVariousEvent and afterVariousEvent properties. Each of these properties is an array of
// references to event listener objects which contain the event methods.
// So, for example, after this call:
// var events = new app.media.Events({ onPlay: function(e){} });
// events.listeners.onPlay[0] is a reference to a new { onPlay: function(e){} } object, and
// events.listeners.onPlay[0].onPlay is a reference to the onPlay method.
app.media.Events = function()
{
this.listeners = {}; // start with empty listeners object
this.dispatching = 0; // not currently dispatching any events
this.removed = {}; // listener names that need delayed removal
this.privAddRemove( arguments, this.privAdd ); // add any event listener object arguments
}
app.media.Events.prototype =
{
// Add any number of event listener objects or other app.media.Events objects.
// events.add() may be called inside an event listener method, and any new listeners that are
// added for the current event will be called for that same event.
// If the same listener object is added twice, the second add is ignored.
// Listeners for a given event (e.g. onClose) are called in the order in which they were added.
// events.add( event listener or app.media.Events object(s) )
add: function()
{
this.privAddRemove( arguments, this.privAdd );
},
// Remove any number of event listener objects or other app.media.Events objects.
// events.remove() may be called inside an event listener method, to remove the current listener
// or any other.
// events.remove( event listener or app.media.Events object(s) )
remove: function()
{
this.privAddRemove( arguments, this.privRemove );
},
// Private method for events.add() and events.remove().
// Loop through all the listener methods in each argument and call doAddRemove for every one.
privAddRemove: function( args, doAddRemove )
{
for( var i = 0; i < args.length; i++ ) // for each event listener object argument in passed array
{
var events = args[i];
if( events.listeners )
{
// It's an app.media.Events object, add or remove every listener in each array
for( var name in events.listeners )
{
var array = events.listeners[name];
for( var i = 0; i < array.length; i++ )
{
doAddRemove.call( this, array[i], name );
}
}
}
else
{
// It's an event listener object, add or remove each method
for( var name in events )
{
// Only interested in onFoo and afterFoo methods, not custom properties
if( name.search(/^on[A-Z]/) == 0 || name.search(/^after[A-Z]/) == 0 )
{
doAddRemove.call( this, events, name );
}
}
}
}
this.privSetDispatch(); // Add or remove the dispatch() method as needed
},
// Private method for events.add().
// Adds a reference to a listener object into events.listeners[name]
// Does nothing if listener already added.
privAdd: function( listener, name )
{
if( typeof(listener) != "object" || typeof(listener[name]) != "function" )
return; // not a valid object and method
var array = this.listeners[name]; // get our existing event listener array
if( ! array )
{
this.listeners[name] = [ listener ]; // no array yet, add array with one listener object
}
else // we have a listener array, append listener to it if it's not already present
{
for( var i = 0; i < array.length; i++ )
{
if( array[i] === listener )
return; // already present, don't add another
}
array[i] = listener; // append listener to array
}
},
// Private method for events.remove().
// Removes a listener object reference from events.listeners[name]
privRemove: function( listener, name )
{
var array = this.listeners[name]; // existing event listener array
if( ! array )
return; // no listeners with this name
for( var i = 0; i < array.length; i++ ) // Look for the listener object in the array
{
if( array[i] === listener )
{
// Found the listener in the array, decide what to do with it
if( this.dispatching ) // Can't remove while dispatching, mark for later removal
array[i] = null, this.removed[name] = this.needCleanup = true;
else if( array.length > 1 )
array.splice( i, 1 ); // Remove listener from array
else
delete this.listeners[name]; // Last one, remove array entirely
return; // Listener is already in the array
}
}
},
// Private function for events.add(), events.remove() and events.privCleanup().
// Sets or deletes the dispatch method depending on whether there are any event listeners.
privSetDispatch: function()
{
for( var name in this.listeners ) // are there any listeners?
{
this.dispatch = this.privDispatch; // found a listener, set the dispatch method
return;
}
delete this.dispatch; // no listeners, remove the dispatch method
},
// events.privDispatch() is a private method that contains the code for events.dispatch().
// To dispatch an event, C++ code calls events.dispatch(), only if that method exists.
// The rest of the event dispatching machinery is implemented in JavaScript.
// We turn event dispatching on and off dynamically by setting and removing the events.dispatch
// property, which is a reference to events.privDispatch().
// If you call events.dispatch() directly from JavaScript, event.target.doc or event.media.doc
// must match the current document.
// You can implement your own event dispatcher from scratch by providing an events object
// with a dispatch method that takes an event argument as this method does.
// This function is reentrant.
privDispatch: function( event )
{
if( !event.media )
event.media = {};
// PDF events may have spaces in their names, so make a copy of event.name with spaces removed
event.media.id = event.name.replace( / /, '' );
// Use doc and events properties in either event.target or in the event object itself
if( event.target )
{
event.media.doc = event.target.doc;
event.media.events = event.target.events;
}
++this.dispatching; // if this.dispatching > 0, events.remove() will use deferred removal
try
{
// First call immediate (onFoo) listener methods
this.privDispatchNow( 'on', event ); // may reenter this function
// Turn stopDispatch off in case an immediate listener turned it on
delete event.stopDispatch;
// If there are any deferred (afterFoo) listeners, post event to queue,
// but don't bother if an immediate listener stopped all dispatching
if( ! event.stopAllDispatch )
if( this.listeners[ 'afterEveryEvent' ] || this.listeners[ 'after' + event.media.id ] )
app.media.priv.postEvent( event );
}
catch( e )
{
app.media.priv.trace( 'di throw: ' + e.message );
}
--this.dispatching;
// If any event listeners were marked for removal while we were dispatching events,
// and we are done with any nested dispatch calls, then clean up the listener arrays.
if( this.needCleanup && ! this.dispatching )
this.privCleanup();
},
// Private method for events.dispatch().
// Clean up any event listener arrays that have had entries marked for removal.
// Each event name that needs to be cleaned up has an entry in this.removed.
// Each listener that is to be removed has been set to null.
privCleanup: function()
{
for( var name in this.removed )
{
var array = this.listeners[name];
for( var i = 0; i < array.length; i++ )
{
if( ! array[i] )
array.splice( i--, 1 ); // Remove listener from array and back up index
}
if( array.length == 0 )
delete this.listeners[name]; // Remove listener array if it's now empty
}
this.removed = {};
this.privSetDispatch(); // Remove the dispatch method if there are no more listeners
delete this.needCleanup;
},
// Private method for events.dispatch().
// Immediately dispatch a single event to all listeners for that event.
// prefix is 'on' or 'after', and event is the event object.
// Calls both EveryEvent listener methods and any specific listener methods for the event.
// This function is reentrant.
privDispatchNow: function( prefix, event )
{
this.privCallMethods( event, prefix + 'EveryEvent' ); // may reenter this function
this.privCallMethods( event, prefix + event.media.id ); // may reenter this function
},
// Private method for events.dispatch().
// Loop through the events.listeners[name] array and call each event listener method found
// there, with 'this' as the event listener object that contains the method.
// If new listeners are added while dispatching, they will also be called.
// This function is reentrant.
privCallMethods: function( event, name )
{
var array = this.listeners[name];
if( array )
{
// Call each listener method in the array
for( var i = 0; i < array.length; i++ )
{
if( event.stopDispatch || event.stopAllDispatch )
break;
var listener = array[i];
if( listener ) // listener is null if removed while dispatching
{
listener[name]( event ); // may reenter this function
}
}
}
},
}
// end app.media.Events.prototype
// A simple event queue.
// app.media.priv.postEvent(event) and app.media.priv.dispatchQueuedEvents() use
// doc.media.priv.queue to manage a per-doc event queue.
// This private method has the same restrictions on calling it as app.media.Events.privDispatch().
app.media.priv.postEvent = function( event )
{
var q = event.media.doc.media.priv.queue;
if( ! q )
q = event.media.doc.media.priv.queue = {};
if( ! q.list )
q.list = [];
q.list.push( event );
if( ! q.timer )
{
q.timer = app.setTimeOut( 'app.media.priv.dispatchPostedEvents(this);', 1, false ); // no disp while modal dlg up
q.timer.media = { doc: event.media.doc }; // allow access to doc from timer obj
}
}
// Called from the short timer set by app.media.priv.postEvent() to dispatch all posted events.
app.media.priv.dispatchPostedEvents = function( doc )
{
try
{
// If doc already closed, bail! Closing doc does NOT unregister timeouts!
// They may or may not fire depending on whether they are GCed before getting fired.
// Event.target is our timeout obj.
if ( event.target.media.doc.closed )
return;
// Grab and delete queue--any new event queued while dispatching will be dispatched later
var q = doc.media.priv.queue;
var list = q.list;
delete q.list;
delete q.timer;
for( var i = 0; i < list.length; i++ )
{
// Stop dispatching "after" events if the doc is closed, checked here in case an
// event method closes the doc. Do not check in privCallMethods--an event method
// that closes the doc should set event.stopDispatchAll.
if( doc.closed )
return;
var e = list[i];
if( e.media.events )
e.media.events.privDispatchNow( "after", e );
}
}
catch( e )
{
app.media.priv.trace( 'dpe throw: ' + e.message );
}
}
// app.media.Markers constructor and prototype
app.media.Markers = function( player )
{
this.player = player;
}
app.media.Markers.prototype =
{
// Finds a marker by name, index number, time, or frame.
// Index numbers are not in any guaranteed order.
// If a time or frame is given, returns the nearest marker at or before that location.
// Returns null if no matching marker is found.
//
// marker = markers.get( cName );
// marker = markers.get({ name: cName });
// marker = markers.get({ index: nIndex });
// marker = markers.get({ time: nSeconds });
// marker = markers.get({ frame: nFrame });
get: function( m )
{
if( ! this.privByIndex )
this.player.privLoadMarkers();
var retMarker = null;
if( this.privByIndex.length > 0 )
{
switch( typeof(m) )
{
case 'string':
retMarker = this.privByName[m];
break;
case 'object':
retMarker = (
m.name !== undefined ? this.privByName[ m.name ] :
m.index !== undefined ? this.privByIndex[ m.index ] :
m.time !== undefined ? this.privFind( 'time', m.time ) :
m.frame !== undefined ? this.privFind( 'frame', m.frame ) :
undefined );
break;
}
}
if( retMarker === undefined )
retMarker = null;
return retMarker;
},
// Private method for markers.get() to find a marker by time or frame.
privFind: function( prop, value )
{
if( value < 0 )
return; // negative time or frame not allowed
var array = this.privByIndex;
var length = array.length;
// Search for nearest marker <= passed value; does not assume any sort order.
var nearIdx;
var nearDist = Infinity;
for( var i = 0; i < length; i++ )
{
// Test for undefined in case some markers have time and some have frame
var v = array[i][prop];
if( v !== undefined )
{
var dist = ( value - v );
if( dist >= 0 && dist < nearDist )
{
// have a new "nearest marker <= value"
nearIdx = i;
nearDist = dist;
}
}
}
if( nearIdx !== undefined )
return array[ nearIdx ];
},
}
// end app.media.Markers.prototype
// app.Monitors constructor and prototype
app.Monitors = function()
{
this.length = 0;
}
app.Monitors.prototype =
{
// monitors.clear()
clear: function()
{
while( this.length > 0 )
delete this[ --this.length ];
},
// monitors.push( value )
// Appends a reference to a monitor object to the array.
push: function( value )
{
this[ this.length++ ] = value;
},
// monitors = monitors.select( monitorType, doc )
// Filter a Monitors array based on an app.media.monitorType value as used in PDF.
// doc is required if monitorType is app.media.monitorType.document or
// app.media.monitorType.nonDocument, otherwise it is ignored.
// Returns new array of references to the selected monitor objects.
select: function( monitorType, doc )
{
switch( monitorType )
{
default:
case app.media.monitorType.document: return this.document(doc).primary();
case app.media.monitorType.nonDocument: return this.nonDocument(doc).primary();
case app.media.monitorType.primary: return this.primary();
case app.media.monitorType.bestColor: return this.bestColor().primary();
case app.media.monitorType.largest: return this.largest().primary();
case app.media.monitorType.tallest: return this.tallest().primary();
case app.media.monitorType.widest: return this.widest().primary();
}
},
// monitors.filter( ranker, minRank )
// Returns a Monitors array containing the monitors that score the highest rank according to
// the ranker function. The ranker function takes a Monitor parameter and returns a numeric or
// boolean rank for it (or any type that can be converted to a number).
// A numeric rank may be any finite value.
// If minRank is not specified, the array returned always contains at least one element (unless
// the original array was already empty).
// If minRank is specified but the final rank is less, the array returned is empty.
// If multiple monitors tie for the highest rank, the returned array contains those monitors in
// the same order as the original array.
filter: function( ranker, minRank )
{
var r = new app.Monitors;
var rank = ( minRank != undefined ? minRank : -Infinity );
for( var i = 0; i < this.length; i++ )
{
// Rank the next Monitor object
var m = this[i];
var mRank = ranker( m );
// If it outranks the best previous ranking, clear the result list.
// If it's the same rank, add it to the result list.
if( mRank >= rank )
{
if( mRank > rank )
r.clear(); // new outranks old, clear result array
r.push( m ); // append new result to any same-ranked results
rank = mRank; // save new rank
}
}
return r;
},
// monitors.bestColor( minColor )
// Returns a Monitors array containing the monitor(s) that have the greatest color depth.
// Returns empty array if minColor is specified and no monitor in the array has a color depth of
// at least minColor bits.
bestColor: function( minColor )
{
return this.filter(
function( m ) { return m.colorDepth; },
minColor );
},
// monitors.bestFit( width, height, bRequire )
// Returns a Monitors array containing the monitor(s) that have at least the specified width and
// height with the least amount of excess area. If all monitors are smaller than the specified
// width and height, then returns an empty array if bRequire is true, or an array of the largest
// available monitors if bRequire is false.
bestFit: function( width, height, bRequire )
{
var tiny = -1000000000;
var area = ( width * height );
return this.filter(
function( m )
{
var mWidth = m.rect[2] - m.rect[0];
var mHeight = m.rect[3] - m.rect[1];
// Rank lowest if it doesn't fit at all, else rank by least excess area
return(
width > mWidth || height > mHeight ? tiny :
area - ( mWidth * mHeight ) );
},
bRequire ? ( tiny + 1 ) : tiny );
},
// monitors.desktop()
// Returns a Monitors array with a single Monitor that represents the entire virtual desktop:
// rect = the union of all Monitor.rect values
// workRect = the union of all the workRect values (may include parts of monitors that are
// outside their workRects).
// colorDepth = the least color depth of any monitor
// isPrimary = (not present)
desktop: function()
{
if( ! this.length )
return [];
var r = { rect: [0,0,0,0], workRect: [0,0,0,0], colorDepth: Number.MAX_VALUE };
for( var i = 0; i < this.length; i++ )
{
var m = this[i];
r.rect = app.media.priv.rectUnion( r.rect, m.rect );
r.workRect = app.media.priv.rectUnion( r.workRect, m.workRect );
r.colorDepth = Math.min( r.colorDepth, m.colorDepth );
}
var result = new app.Monitors;
result.push( r );
return result;
},
// monitors.document( doc, bRequire )
// Returns a Monitors array containing the monitor(s) that display the greatest amount of the
// specified document.
// If bRequire is true, returns empty array if the document does not appear on any monitor.
// If bRequire is false and document does not appear on any monitor, returns array containing
// all monitors.
document: function( doc, bRequire )
{
return this.mostOverlap( doc.outerDocWindowRect, bRequire ? 1 : undefined );
},
// monitors.largest( minArea )
// Returns a Monitors array containing the monitor(s) with the greatest area.
// Returns empty array if minArea is specified and the greatest area is less than that.
largest: function( minArea )
{
return this.filter(
function( m ) { return app.media.priv.rectArea( m.rect ); },
minArea );
},
// monitors.leastOverlap( rect, maxOverlapArea )
// Returns a Monitors array containing the monitor(s) which have the least area overlapping rect.
// Returns empty array if maxOverlapArea is specified and all monitors overlap rect by a greater
// amount.
leastOverlap: function( rect, maxOverlapArea )
{
if( maxOverlapArea !== undefined ) // if undefined must stay undefined (-undefined is NAN)
maxOverlapArea = -maxOverlapArea;
return this.filter(
function( m ) { return -app.media.priv.rectIntersectArea( m.rect, rect ); }, maxOverlapArea );
},
// monitors.mostOverlap( rect, minOverlapArea )
// Returns a Monitors array containing the monitor(s) which have the most area overlapping rect.
// Returns empty array if minOverlapArea is specified and there is no monitor with at least that
// much overlap.
mostOverlap: function( rect, minOverlapArea )
{
return this.filter(
function( m ) { return app.media.priv.rectIntersectArea( m.rect, rect ); },
minOverlapArea );
},
// monitors.nonDocument( doc, bRequire )
// Returns a Monitors array containing the monitor(s) that display none of, or the least amount
// of the specified document.
// If parts of the document appear on every monitor, then returns empty array if bRequire is true
// or a copy of the original monitors array if bRequire is false.
nonDocument: function( doc, bRequire )
{
return this.leastOverlap( doc.outerDocWindowRect, bRequire ? 0 : undefined );
},
// monitors.primary()
// Returns a Monitors array containing at most one entry, the primary monitor.
primary: function()
{
return this.filter(
function( m ) { return m.isPrimary; },
1 );
},
// monitors.secondary()
// Returns a Monitors array containing all secondary (non-primary) monitors.
secondary: function()
{
return this.filter(
function( m ) { return ! m.isPrimary; },
1 );
},
// monitors.tallest( minHeight )
// Returns a Monitors array containing the monitor(s) with the greatest height.
tallest: function( minHeight )
{
return this.filter(
function( m ) { return m.rect[3] - m.rect[1]; },
minHeight );
},
// monitors.widest( minWidth )
// Returns a Monitors array containing the monitor(s) with the greatest width.
widest: function( minWidth )
{
return this.filter(
function( m ) { return m.rect[2] - m.rect[0]; },
minWidth );
},
}
// end app.Monitors.prototype
// app.media.Players constructor and prototype
app.media.Players = function()
{
this.length = 0;
}
app.media.Players.prototype =
{
// Players.clear()
clear: function()
{
while( this.length > 0 )
delete this[ --this.length ];
},
// Players.push( value )
// Appends a reference to a Player object to the array.
push: function( value )
{
this[ this.length++ ] = value;
},
// players = Players.select( args )
// Filters a Players array based on any of the PlayerInfo properties.
// The array is not in any particular order.
// The object argument lists the properties to filter on.
// String properties (id, name, version) can use either strings or regular expressions.
// Example: get all players with 'QuickTime' in the id:
// var p = app.media.getPlayers().select({ id: /QuickTime/ });
// All specified properties must be present and must match exactly (or must pass regex match).
// If no properties are specified, all Players in the array will be present in the returned array.
// If no players in the array match the search criteria, returns an empty Players array.
select: function( args )
{
var r = new app.media.Players;
for( var i = 0; i < this.length; i++ )
{
var info = this[i]; // Get the PlayerInfo object
var ok = true;
for( var prop in args ) // check each property that the caller passed in
{
if( !( prop in info ) )
return []; // unknown selection property, probably future PDF version, give up
// Handle either a regular expression or a string, number, or boolean comparison
if( args[prop].exec ? args[prop].exec(info[prop]) == null : args[prop] != info[prop] )
{
ok = false;
break;
}
}
if( ok )
r.push( info ); // passed all tests, append reference to PlayerInfo
}
return r;
},
}
// end app.media.Players.prototype
// app.media.MediaPlayer constructor and prototype
app.media.MediaPlayer = function()
{
}
app.media.MediaPlayer.prototype =
{
// MediaPlayer.open()
open: function()
{
var ret;
try
{
// Add stock annot events and cross-references only if we open the movie
if( this.annot )
{
app.media.priv.AddStockEventsHelper( this.annot, app.media.getAnnotStockEvents( this.settings.windowType ) );
this.annot.player = this;
}
ret = this.privOpen.apply( this, arguments );
if( ret.code != app.media.openCode.success )
app.media.removeStockEvents( this );
}
catch( e )
{
app.media.removeStockEvents( this );
throw e;
}
return ret;
},
}
// end app.media.MediaPlayer.prototype
// Determine whether any media playback is allowed and return true if it is.
// If playback is not allowed, then alert the user and return false.
// bCanPlay = app.media.canPlayOrAlert({ doc: Doc });
app.media.canPlayOrAlert = function( args )
{
var canPlay = args.doc.media.canPlay;
if( canPlay.yes )
return true; // Playback is allowed
app.media.alert( 'CannotPlay', args, { canPlayResult: canPlay } );
return false;
}
// Return a settings object to play a rendition, if all playback requirements are met.
// Otherwise return settings to "play" alt text, if showAltText and showEmptyAltText allow and
// alt text is available, or else return null.
// settings = app.media.getRenditionSettings({
// doc: Doc,
// settings: MediaSettings, /* Optional, shallow-copied into returned settings */
// rendition: MediaRendition or RenditionList,
// showAltText: Boolean, /* Optional, default = false */
// showEmptyAltText: boolean, /* Optional, default = false */
// fromUser: boolean /* Optional, default = false */
// });
app.media.getRenditionSettings = function( args )
{
var settings;
var selection = args.rendition.select( true );
if( selection.rendition )
{
try
{
// Get playback settings from rendition - throws on failure, never returns null
settings = selection.rendition.getPlaySettings( true );
settings.players = selection.players;
app.media.priv.copyProps( args.settings, settings ); // copy the user's settings
return settings;
}
catch( e )
{
// FNF or open failure? Rethrow the exception unless we can handle it here
if( e.name != "RaiseError" )
throw e;
if( e.raiseSystem != app.media.raiseSystem.fileError )
throw e;
if( e.raiseCode != app.media.raiseCode.fileNotFound &&
e.raiseCode != app.media.raiseCode.fileOpenFailed )
throw e;
app.media.alert( 'FileNotFound', args, { fileName: selection.rendition.fileName } );
}
}
else // no rendition in selection
{
app.media.alert( 'SelectFailed', args, { selection: selection } );
}
// Did we fail after finding a rendition? If so, use its alt text if allowed
return app.media.getAltTextSettings( args, selection );
}
// Return the first media rendition in a rendition list, or null if there is no match.
app.media.getFirstRendition = function( list )
{
for( var i = 0; i < list.length; i++ )
{
if( list[i].rendition.type == app.media.renditionType.media )
return list[i].rendition;
}
return null;
}
// Return a settings object with a data property to play a URL.
// Any properties in args.settings are shallow-copied into the returned settings object.
// settings = app.media.getURLSettings({
// URL: String, /* required */
// mimeType: String, /* optional */
// settings: MediaSettings /* optional */
// });
app.media.getURLSettings = function( args )
{
// Get a data object for the URL and MIME type
var settings =
{
data: app.media.getURLData( args.URL, args.mimeType )
}
app.media.priv.copyProps( args.settings, settings ); // copy the user's settings
return settings;
}
// Return an alt text settings object for a selection, or null if there's no alt text available,
// or if alt text should not be used in this situation.
// Arguments are the same as app.media.getRenditionSettings().
// Any properties in args.settings are shallow-copied into the returned settings object.
app.media.getAltTextSettings = function( args, selection )
{
if( ! args.showAltText )
return null;
var rendition = selection.rendition || app.media.getFirstRendition( selection.rejects );
if( ! rendition )
return null;
settings = rendition.getPlaySettings( false );
app.media.priv.copyProps( args.settings, settings ); // copy the user's settings
// Use alt text only when docked (compute default windowType first if needed)
if( ! settings.windowType )
settings.windowType = app.media.priv.computeDefaultWindowType( args, settings );
if( settings.windowType != app.media.windowType.docked )
return null;
// Get the alt text, or default text if none specified and showEmptyAltText is true
var text = rendition.altText;
if( text.length == 0 )
{
if( ! args.showEmptyAltText )
return null;
text = app.media.priv.getString( "IDS_ERROR_NO_ALT_TEXT_SPECIFIED" );
}
settings.data = app.media.getAltTextData( text );
settings.players = [ app.media.priv.altTextPlayerID ];
return settings;
}
// Add the standard event listeners to a player.
// If annot is specified, set up so when the player is opened, the annot will have its standard
// event listeners attached. The player.annot and annot.player cross-references will be
// installed at the same time.
// The player must have a settings property. In the settings property, windowType and visible
// are the only values used here. The visible property may be modified here and restored later
// in the afterReady listener.
app.media.addStockEvents = function( player, annot )
{
if( player.stockEvents )
return; // already added stock events
app.media.priv.AddStockEventsHelper( player, app.media.getPlayerStockEvents( player.settings ) );
if( annot )
{
// remember that annot needs stock events attached when player is opened
player.annot = annot;
}
}
// Private function to add stock events to an object. Saves a reference to the original stock
// events in object.stockEvents for later removal. object.stockEvents must not be modified after
// it is saved here, or removal will not work correctly.
app.media.priv.AddStockEventsHelper = function( object, events )
{
object.stockEvents = events;
if( ! object.events )
object.events = new app.media.Events;
object.events.add( events )
}
// Remove the standard event listeners and cross-references from a player and its associated annot.
// Does nothing if no stock events (never added or already removed).
app.media.removeStockEvents = function( player )
{
if( ! player || ! player.stockEvents )
return;
function removeProps( object )
{
if( object.events )
{
object.events.remove( object.stockEvents );
delete object.stockEvents;
}
}
removeProps( player );
if( player.annot )
{
if( player.annot.stockEvents )
removeProps( player.annot );
delete player.annot.player;
delete player.annot;
}
}
// Return floating window rect for a doc, floating params, monitor to play on, and
// optional array containing the dimensions [l,r,t,b] of any additional controller UI.
// NOTE: this method is called from both JS and C++ code, so do not change its signature
// without great care!
app.media.computeFloatWinRect = function( doc, floating, whichMonitor, uiSize )
{
// Figure out rect in virtual desktop space that we are positioning relative to
var overRect;
switch( floating.over )
{
default:
case app.media.over.pageWindow:
overRect = doc.pageWindowRect;
break;
case app.media.over.appWindow:
// Inner more consistent placement because no borders etc.
overRect = doc.innerAppWindowRect;
break;
case app.media.over.desktop:
overRect = app.monitors.desktop()[0].rect;
break;
case app.media.over.monitor:
overRect = app.monitors.select( whichMonitor, doc )[0].workRect;
break;
}
// Get the border sizes for this window
var border = app.media.getWindowBorderSize( floating );
// Align floating window with overRect according to align, using the
// floating window rect plus the border sizes
rect = app.media.priv.rectAlign(
overRect, floating.align,
floating.width + border[0] + border[2],
floating.height + border[1] + border[3] );
// Grow the rect by the UI size (if any)
if( uiSize )
rect = app.media.priv.rectGrow( rect, uiSize );
return rect;
}
// Return a new instance of the standard player events for the given settings.
// In the settings property, windowType and visible are the only values used here.
// The settings.visible property may be modified here, and restored later in an afterReady event.
// If you call this method directly and there is an annot associated with the player, you must
// set player.annot and annot.player as shown in addStockEvents().
app.media.getPlayerStockEvents = function( settings )
{
var events = new app.media.Events;
if( app.media.trace )
events.add( app.media.getPlayerTraceEvents() );
events.add(
{
onClose: function( e )
{
var annot = e.target.annot;
app.media.removeStockEvents( e.target ); // must do this before setFocus call below
if( annot )
{
annot.extFocusRect = null;
// If docked screen had focus when closed, and further playback is allowed,
// put focus back on annot
if( e.media.hadFocus &&
e.target.settings.windowType == app.media.windowType.docked &&
e.media.doc.media.canPlay.yes )
{
// Allow async setFocus since we're in event method
// Does not fire stock annot Focus event because stock events removed above
annot.setFocus( true );
}
}
},
afterDone: function( e )
{
e.target.close( app.media.closeReason.done ); // fires Close and may fire Blur
},
afterError: function( e )
{
app.media.alert( 'PlayerError', e.target.args, { errorText: e.media.text } );
e.target.close( app.media.closeReason.error ); // fires Close and may fire Blur
},
afterEscape: function( e )
{
e.target.close( app.media.closeReason.uiScreen ); // fires Close and may fire Blur
}
});
// Add player event listeners for specific window types
switch( settings.windowType )
{
case app.media.windowType.docked:
{
events.add(
{
onGetRect: function( e )
{
if( e.target.annot )
{
// Get the annot's rectangle and expand it to include any
// visible media player user interface. Return this rectangle in
// the event object, and also use it as the annot's focus rect.
e.target.annot.extFocusRect = e.media.rect =
app.media.priv.rectGrow(
e.target.annot.innerDeviceRect, e.target.uiSize );
}
},
onBlur: function( e )
{
if( e.target.annot )
e.target.annot.alwaysShowFocus = false;
},
onFocus: function( e )
{
if( e.target.annot )
e.target.annot.alwaysShowFocus = true;
}
});
}
break;
case app.media.windowType.floating:
{
// Need either a rect or a width and height
if ( !settings.floating.rect && ( !settings.floating.width || !settings.floating.height ) )
app.media.priv.throwBadArgs(); // throw exception
if( settings.visible === undefined )
settings.visible = app.media.defaultVisible;
if( settings.visible )
{
// Hide floating window while it's being created, then show it after the
// controller dimensions are available
settings.visible = false;
events.add(
{
afterReady: function( e )
{
var floating = e.target.settings.floating;
var rect = floating.rect; // take user-provided rect, or calculate one
if( ! rect )
{
rect = app.media.computeFloatWinRect( e.media.doc, floating,
e.target.settings.monitorType, e.target.uiSize );
}
else
{
// Grow passed rect by UI size
rect = app.media.priv.rectGrow( rect, e.target.uiSize );
}
// Are we supposed to move the window onscreen if it is offscreen?
if( floating.ifOffScreen == app.media.ifOffScreen.forceOnScreen )
{
// Make sure window rect is totally onscreen, NOP if onscreen already
rect = app.media.constrainRectToScreen( rect,
app.media.priv.rectAnchorPt( rect, floating.align ) );
}
// Set the outer rect
e.target.outerRect = rect;
// Show the window and give it the focus
e.target.visible = true;
e.target.setFocus(); // fires Focus event
}
});
}
}
break;
}
return events;
}
// Return a new instance of the debug trace event listeners for a player.
app.media.getPlayerTraceEvents = function()
{
return new app.media.Events(
{
onEveryEvent: function( e )
{
if( e.media.id != 'GetRect' ) // cannot trace inside onGetRect, it can hang Acrobat
app.media.priv.trace( 'player event: on' + e.media.id );
},
afterEveryEvent: function( e )
{
app.media.priv.trace( 'player event: after' + e.media.id );
},
onScript: function( e )
{
app.media.priv.trace( "player onScript('" + e.media.command + "','" + e.media.param + "')" );
},
afterScript: function( e )
{
app.media.priv.trace( "player afterScript('" + e.media.command + "','" + e.media.param + "')" );
},
onStatus: function( e )
{
app.media.priv.trace( "player onStatus: " +
( e.media.progress >= 0 ? e.media.progress + "/" + e.media.total + ", " : "" ) +
" status code: " + e.media.code + ": '" + e.media.text + "'" );
},
afterStatus: function( e )
{
app.media.priv.trace( "player afterStatus: " +
( e.media.progress >= 0 ? e.media.progress + "/" + e.media.total + ", " : "" ) +
" status code: " + e.media.code + ": '" + e.media.text + "'" );
}
});
}
// Return a new instance of the standard annot events:
// For a docked player, handle Focus and Blur to give the player the focus instead of the annot.
// For any type of player, close the player on Destroy.
app.media.getAnnotStockEvents = function( windowType )
{
var events = new app.media.Events;
if( app.media.trace )
events.add( app.media.getAnnotTraceEvents() );
events.add(
{
onDestroy: function( e )
{
if( e.target.player )
{
// NOP if not open
// fires Close and possibly other events
e.target.player.close( app.media.closeReason.docChange );
}
},
} );
if( windowType == app.media.windowType.docked )
{
events.add(
{
onFocus: function( e )
{
// If player is open, give it the focus. This event could be fired while doing
// UI inside player.open() or the like.
if( e.target.player.isOpen )
{
e.target.player.setFocus(); // fires Focus for player and Blur for annot
}
// Prevent any action from being fired for the Focus event, since focus
// has already been removed inside setFocus(). If setFocus() not called,
// we're in the process of some sort of UI and we don't want random actions
// firing either.
e.stopDispatch = true;
},
onBlur: function( e )
{
// As with the Focus event, prevent any action from being fired for the Blur event.
// This also prevents anybody after us from seeing onBlur before onFocus because
// of our setFocus() call within onFocus.
e.stopDispatch = true;
}
});
}
return events;
}
// Return a new instance of the debug trace event listeners for an annot.
app.media.getAnnotTraceEvents = function()
{
return new app.media.Events(
{
onEveryEvent: function( e )
{
app.media.priv.trace( 'annot event: on' + e.media.id );
},
afterEveryEvent: function( e )
{
app.media.priv.trace( 'annot event: after' + e.media.id );
}
});
}
// Make a shallow copy of args and run our "Do What I Mean" logic on it, to fill in default values
// used in app.media.createPlayer(). The original args object is not modified, and changes made
// later to the copy do not affect the original. Objects inside args are shared between the
// original and the copy, and changes made inside these objects are visible from both args objects.
// If args.annot or args.rendition are not defined, gets them from current event object.
// If args.doc is not defined, gets it from args.annot or args.rendition.
// Throws exception on failure.
app.media.argsDWIM = function( args )
{
if( args && args.privDWIM )
return args; // already did a DWIM copy
args = app.media.priv.copyProps( args );
args.privDWIM = true;
// Use annot and rendition passed in parameters, or get them from event object
if( event && event.action )
{
if( ! args.annot )
args.annot = event.action.annot; // TODO: it'd be nice to verify type of annot here...
if( ! args.rendition )
args.rendition = event.action.rendition;
}
// Get doc from rendition or annot if args.doc not provided
if( ! args.doc )
{
if( args.rendition && args.annot )
if( args.rendition.doc != args.annot.doc )
app.media.priv.throwBadArgs();
if( args.rendition )
args.doc = args.rendition.doc;
else if( args.annot )
args.doc = args.annot.doc;
}
// If fromUser is not specified, use !! to set it to true or false based on event name
if( args.fromUser === undefined )
args.fromUser = !!( event && event.name && ! app.media.pageEventNames[event.name] );
if( args.showAltText === undefined )
args.showAltText = true;
if( args.showEmptyAltText === undefined )
args.showEmptyAltText = ! args.fromUser;
return args;
}
// Private function for app.media.priv.createPlayer().
app.media.priv.createPlayer = function( args )
{
app.media.priv.trace( "app.media.priv.createPlayer" );
if( ! args.doc )
app.media.priv.throwBadArgs(); // doc is required
if( ! app.media.canPlayOrAlert( args ) )
return null; // playback is not allowed, user has been notified
if( args.annot && args.annot.player )
{
args.annot.player.close( app.media.closeReason.play ); // fires events
// args.annot.player presumably is null now, unless onClose didn't null it out.
// Cannot create new player in onClose so shouldn't have any issues there
}
var player = args.doc.media.newPlayer({ args: args });
// Get a settings object for either a URL or a rendition, whichever was provided
// URL wins if both present.
player.settings =
args.URL ? app.media.getURLSettings( args ) :
args.rendition ? app.media.getRenditionSettings( args ) :
app.media.priv.throwBadArgs(); // need either rendition or URL
if( ! player.settings )
return null; // user has been notified already
// If no windowType, compute default value
if( ! player.settings.windowType )
player.settings.windowType = app.media.priv.computeDefaultWindowType( args, player.settings );
// If windowType couldn't be computed, throw
if( ! player.settings.windowType )
app.media.priv.throwBadArgs();
switch( player.settings.windowType )
{
case app.media.windowType.docked:
{
if( player.settings.page === undefined )
{
if( ! args.annot )
app.media.priv.throwBadArgs(); // need either an annot or a page number
player.settings.page = args.annot.page;
}
}
break;
case app.media.windowType.fullScreen:
{
player.settings.monitor = app.monitors.select( player.settings.monitorType, args.doc );
}
break;
}
// Add any stock events to the player (and set up to add them to the annot later if needed).
// Even if the player is never opened, no need to remove them since they won't get used.
if( ! args.noStockEvents )
app.media.addStockEvents( player, args.annot );
if( args.events )
{
if( ! player.events )
player.events = new app.media.Events;
player.events.add( args.events ); // Add caller's custom events
}
return player;
}
// Private function to get default windowType:
// docked if there is an annot,
// floating if there is no annot and there is a floating settings obj,
// undefined otherwise
app.media.priv.computeDefaultWindowType = function( args, settings )
{
var retWT;
if( args.annot )
retWT = app.media.windowType.docked;
else if( settings.floating )
retWT = app.media.windowType.floating;
return retWT;
}
// Display an alert, given an alert name (e.g. FileNotFound), createArgs object (the type of object
// processed by app.media.argsDWIM()), and optional specificArgs object which contains information
// specific to the alert. The only property that MUST be present in createArgs is doc.
// NOTE: This implementation of app.media.alert is for private use within media.js only.
// Do not use it in PDF files! It will be changed in a future release.
app.media.alert = function( name, createArgs, specificArgs )
{
var alertArgs = { name: name, createArgs: createArgs, alerterData: {} }
app.media.priv.copyProps( specificArgs, alertArgs );
// Set the default doc alerts if they are not already set
if( !( 'alerts' in createArgs.doc.media ) )
createArgs.doc.media.alerts = new app.media.Alerts;
// If there are no alerts, use the doc alerts
var curAlerts = createArgs.alerts;
if ( !curAlerts )
curAlerts = createArgs.doc.media.alerts;
// Keep on dispatching up parent chain of alerters until finally handled or no more alerters
var gpMN = 'getParent';
var handled = false;
var lastAlerts = null;
while ( curAlerts && !handled )
{
lastAlerts = curAlerts;
// dispatch
handled = dispatchAlert( curAlerts );
if ( !handled )
{
// not handled, try to move to parent alerters
var newAlerts = null;
if ( gpMN in curAlerts )
{
newAlerts = curAlerts[ gpMN ]( alertArgs );
if ( newAlerts )
{
// setup so parent alter can see child's alertArgs if it wants to
// and to give reference to child alerter
var newCI = {};
newCI.alerts = curAlerts;
newCI.alerterData = alertArgs.alerterData;
newCI.next = alertArgs.childInfo;
alertArgs.childInfo = newCI;
// Clear stuff out so new alerter gets fresh shot
alertArgs.alerterData = {};
delete alertArgs.stop;
delete alertArgs.sendToParent;
}
}
curAlerts = newAlerts;
}
}
// In inverse order, call afterParent as needed (not for top of course)
curAlerts = lastAlerts;
while ( alertArgs.childInfo ) {
var newPI = {};
newPI.alerts = curAlerts;
newPI.alerterData = alertArgs.alerterData;
newPI.next = alertArgs.parentInfo;
alertArgs.parentInfo = newPI;
alertArgs.alerterData = alertArgs.childInfo.alerterData;
curAlerts = alertArgs.childInfo.alerts;
alertArgs.childInfo = alertArgs.childInfo.next;
var method = curAlerts[ 'afterParent' ];
if( typeof method == 'function' )
method.call( curAlerts, alertArgs, handled );
}
// Local function to dispatch to an alerts object and return true if handled
function dispatchAlert( alerts )
{
var other = 'alertOther';
var alertMtdName = 'alert' + name;
if( alertMtdName in alerts || other in alerts )
{
callMethod( 'before' );
if ( ! callMethod( alertMtdName ) )
callMethod( other );
callMethod( 'after' );
return !alertArgs.sendToParent;
}
// Local function to call a single alerts method and return true if it is present
function callMethod( methodName )
{
if( methodName in alerts && ! alertArgs.stop && !alertArgs.sendToParent )
{
var method = alerts[methodName];
if( typeof method == 'function' )
method.call( alerts, alertArgs );
return true;
}
}
return false;
}
}
// app.media.Alerts - constructs a stock alerts object
// If an alert is the result of a user-triggered event, always show alert with no checkbox.
// If not user triggered, we include a "don't show again" checkbox and save its state,
// but don't show the alert if the user has previously checked that box in this document.
// The "don't show again" state is shared by all alerts (not tracked for each different type).
// NOTE: This implementation of app.media.Alerts is for private use within media.js only.
// Do not use it in PDF files! It will be changed in a future release.
app.media.Alerts = function()
{
return {
before: function( alertArgs )
{
if( ! alertArgs.createArgs.fromUser && this.skip )
alertArgs.stop = true;
},
after: function( alertArgs )
{
if( ! alertArgs.createArgs.fromUser )
this.skip = alertArgs.alerterData.skip;
},
alertCannotPlay: function( alertArgs )
{
var canPlay = alertArgs.canPlayResult;
if( ! canPlay.canShowUI )
alertArgs.stop = true; // Playback not allowed and may not show UI -- suppress after method
else
{
if( canPlay.no.authoring )
{
// Playback is not allowed while authoring
alertArgs.alerterData.skip = this.privOK( alertArgs.createArgs, "IDS_PLAYBACK_DISALLOWED_WHILE_AUTHORING" );
}
else if( canPlay.no.security )
{
// User prefs say "no multimedia"
alertArgs.alerterData.skip = this.privOK( alertArgs.createArgs, "IDS_PLAYBACK_DISALLOWED_CONFIGURATION" );
}
else
{
// as of now, will not get here -- should probably put up generic error though just in case...
}
}
},
alertException: function( alertArgs )
{
// No don't show again checkbox for exceptions
app.alert( alertArgs.error.message );
},
alertFileNotFound: function( alertArgs )
{
alertArgs.alerterData.skip = app.media.alertFileNotFound( alertArgs.createArgs.doc, alertArgs.fileName, ! alertArgs.createArgs.fromUser );
},
alertOpen: function( alertArgs )
{
// TODO - show UI here, except if already shown (e.g. for failPlayerSecurityPrompt)
// may want more info from open (e.g. to tell us if UI was shown already)
},
alertPlayerError: function( alertArgs )
{
alertArgs.alerterData.skip = this.privOK( alertArgs.createArgs, "IDS_JS_PLAYBACK_ERROR", alertArgs.errorText );
},
alertSelectFailed: function( alertArgs )
{
alertArgs.alerterData.skip = app.media.alertSelectFailed(
alertArgs.createArgs.doc, alertArgs.selection.rejects, ! alertArgs.createArgs.fromUser, alertArgs.createArgs.fromUser );
},
alertOther: null, // ignore any other alerts
// Display a simple "OK" alert with optional "don't show again" checkbox.
// Return checkbox result value, or undefined if no checkbox.
privOK: function( createArgs, idMsg, strAppend )
{
var a = { cMsg: app.media.priv.getString(idMsg) + ( strAppend || '' ),
nIcon: 0, nType: 0, oDoc: createArgs.doc };
if( ! createArgs.fromUser )
a.oCheckbox = { cMsg: app.media.priv.getString("IDS_DONOT_SHOW_AGAIN_DOC"),
bInitialValue: false };
app.alert( a );
if( a.oCheckbox )
return a.oCheckbox.bAfterValue;
},
}
}
// Debugging code
app.media.priv.dumpObject = function( obj, str, bValues )
{
if( ! str )
str = "";
else
str += " ";
str += "(" + obj + ") [" + typeof(obj) + "]\n";
for( var prop in obj )
str += " " + prop + ( bValues ? ": " + obj[prop] : "" ) + "\n";
app.media.priv.trace( str );
}
app.media.priv.dumpNames = function( obj, str )
{
app.media.priv.dumpObject( obj, str, false );
}
app.media.priv.dumpValues = function( obj, str )
{
app.media.priv.dumpObject( obj, str, true );
}
app.media.priv.dumpArray = function( array, str )
{
if( ! str )
str = "";
else
str += " ";
str += "(" + array + ") [" + typeof(array) + "]\n{ ";
/*
if( array.length )
app.alert( "has length" );
else
app.alert( "no length" );
*/
for( var i = 0; i < array.length; i++ )
str += array[i] + ( i < array.length - 1 ? ", " : " }" );
app.media.priv.trace( str );
}
app.media.priv.trace = function( str )
{
if( app.media.trace )
console.println( str );
}
// Private function called in a rendition action. Puts up UI on failure.
app.media.priv.stopAnnotPlayer = function()
{
try
{
annot = event.action.annot;
if( annot.player )
annot.player.close( app.media.closeReason.stop ); // fires Close event, may fire Blur
}
catch( e )
{
app.alert( e.message );
}
}
// Private function called in a rendition action. NOP if already paused. Puts up UI on failure.
app.media.priv.pauseAnnotPlayer = function()
{
try
{
annot = event.action.annot;
if( annot.player )
annot.player.pause();
}
catch( e )
{
app.alert( e.message );
}
}
// Private function called in a rendition action. NOP if not paused. Puts up UI on failure.
app.media.priv.resumeAnnotPlayer = function()
{
try
{
annot = event.action.annot;
if( annot.player )
annot.player.play();
}
catch( e )
{
app.alert( e.message );
}
}
// Enumerate and copy properties from one object to another object or to a new object, and
// return the resulting object. This is a shallow copy: any objects referenced by the from
// object will now be referenced by both the from and to objects.
app.media.priv.copyProps = function( from, to )
{
if( ! to )
to = {};
if( from )
{
for( var name in from )
to[name] = from[name];
}
return to;
}
// Rectangle utility functions
// Tables used by rectAlign to map app.media.align values to window positioning multipliers.
// see app.media.align.* un tl tc tr cl c cr bl bc br
app.media.priv.xPosTable = [ 0.5, 0.0, 0.5, 1.0, 0.0, 0.5, 1.0, 0.0, 0.5, 1.0 ];
app.media.priv.yPosTable = [ 0.5, 0.0, 0.0, 0.0, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0 ];
// Given an app.media.align value and a desired width and height, return a rectangle
// aligned with rect according to the align value.
app.media.priv.rectAlign = function( rect, align, width, height )
{
if( ! align )
align = app.media.align.center;
var x = rect[0] + ( rect[2] - rect[0] - width ) * app.media.priv.xPosTable[align];
var y = rect[1] + ( rect[3] - rect[1] - height ) * app.media.priv.yPosTable[align];
return [ x, y, x + width, y + height ];
}
// Given an app.media.align value and a rect, return an anchor point
app.media.priv.rectAnchorPt = function( rect, align )
{
if( ! align )
align = app.media.align.center;
var x = rect[0] + ( ( rect[2] - rect[0] ) * app.media.priv.xPosTable[align] );
var y = rect[1] + ( ( rect[3] - rect[1] ) * app.media.priv.yPosTable[align] );
return [ x, y ];
}
// Returns the area of a rectangle. If rect is empty, 0 is returned.
app.media.priv.rectArea = function( rect )
{
if( app.media.priv.rectIsEmpty( rect ) ) // empty rect might be [10,10,0,0] so cannot just do the math
return 0;
else
return ( rect[2] - rect[0] ) * ( rect[3] - rect[1] );
}
// Returns rect grown by size, an array of four values giving the amount to grow each edge.
// Returned rect is a new object, input rect is not modified.
app.media.priv.rectGrow = function( rect, size )
{
return [
rect[0] - size[0],
rect[1] - size[1],
rect[2] + size[2],
rect[3] + size[3]
];
}
// Returns the intersection of two rectangles.
// If either input rect is empty, or there is no intersection, [0,0,0,0] is returned.
// Returned rect is a new object, input rects are not modified.
app.media.priv.rectIntersect = function( rectA, rectB )
{
var newRect;
if( app.media.priv.rectIsEmpty(rectA) || app.media.priv.rectIsEmpty(rectB) )
{
newRect = [ 0, 0, 0, 0 ];
}
else
{
newRect =
[
Math.max( rectA[0], rectB[0] ),
Math.max( rectA[1], rectB[1] ),
Math.min( rectA[2], rectB[2] ),
Math.min( rectA[3], rectB[3] )
];
if( app.media.priv.rectIsEmpty( newRect ) )
newRect = [ 0, 0, 0, 0 ];
}
return newRect;
}
// Take the intersection of two rectangles and return its area.
app.media.priv.rectIntersectArea = function( rectA, rectB )
{
return app.media.priv.rectArea( app.media.priv.rectIntersect( rectA, rectB ) );
}
// Is a rectangle empty?
app.media.priv.rectIsEmpty = function( rect )
{
return ! rect || rect[0] >= rect[2] || rect[1] >= rect[3];
}
// Returns new object that contains same values (not custom values) as input rect.
app.media.priv.rectCopy = function( rect )
{
return [ rect[0], rect[1], rect[2], rect[3] ];
}
// Returns the union of two rectangles.
// Returned rect is a new object, input rects are not modified.
app.media.priv.rectUnion = function( rectA, rectB )
{
return(
app.media.priv.rectIsEmpty(rectA) ? app.media.priv.rectCopy( rectB ) :
app.media.priv.rectIsEmpty(rectB) ? app.media.priv.rectCopy( rectA ) :
[
Math.min( rectA[0], rectB[0] ),
Math.min( rectA[1], rectB[1] ),
Math.max( rectA[2], rectB[2] ),
Math.max( rectA[3], rectB[3] )
]
);
}
// Get a resource string
app.media.priv.getString = function( idString )
{
return app.getString( 'Multimedia', idString );
}
// Return a value or a default if the value is undefined
app.media.priv.valueOr = function( value, def )
{
return value !== undefined ? value : def;
}
// Private constants
app.media.priv.altTextPlayerID = 'vnd.adobe.swname:ADBE_AltText';
// End of media.js